import ByteBuffer from "./bytebuffer";

export default class packet {
    private _head: number = 0;
    private _buffer: Buffer | undefined = undefined;

    constructor(head?: number, buffer?: Buffer) {
        if (head != undefined) {
            this._head = head;
        }
        if (buffer != undefined) {
            this._buffer = buffer;
        }
    }

    set head(h: number) {
        this._head = h;
    }

    get head() {
        return this._head;
    }

    private static readVal(val: any, buff: ByteBuffer) {
        if (typeof val === 'number') {
            val = buff.int64();
        } else if (typeof val === 'string') {
            val = buff.string();
        } else if (typeof val === 'object') {
            if (Array.isArray(val)) {
                let len = buff.short();
                for (let j = 0; j < len; j++) {
                    val[j] = this.readType(buff);
                }
            } else {
                val = this.readObj(val, buff);
            }
        } else if (typeof val === 'boolean') {
            let b = buff.short();
            val = b == 1 ? true : false;
        } else if (typeof val == "bigint") {
            val = buff.int64();
        }
        return val;
    }

    private static readArray(arr: any[], buff: ByteBuffer) {
        let len = buff.short();
        for (let j = 0; j < len; j++) {
            let type = this.readType(buff);
            arr[j] = (this.readVal(arr[j], buff));
        }
    }

    private static readObj(obj: any, buff: ByteBuffer) {
        let keys = Object.keys(obj);
        keys.sort();
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i];
            obj[key] = this.readVal(obj[key], buff);
        }
        return obj;
    }
    private static readType(buff: ByteBuffer) {
        let type = buff.short();
        if (type == 0) {
            return buff.int64();
        } else if (type == 1) {
            return buff.string();
        } else if (type == 2) {
            return this.readArray([], buff);
        } else if (type == 3) {
            return this.readObj({}, buff);
        }
        return null;
    }
    private static writeType(val: any, buff: ByteBuffer) {
        if (typeof val === 'number') {
            buff.short(0);
            buff.int64(val);
        } else if (typeof val === 'string') {
            buff.short(1);
            buff.string(val);
        } else if (typeof val === 'object') {
            if (Array.isArray(val)) {
                buff.short(2);
                buff.short(val.length);
                this.writeArray(val, buff);
            } else {
                console.error("暂不支持 type写入 obj");
            }
        }
    }

    private static writeVal(val: any, buff: ByteBuffer) {
        if (typeof val === 'number') {
            buff.int64(val);
        } else if (typeof val === 'string') {
            buff.string(val);
        } else if (typeof val === 'object') {
            if (Array.isArray(val)) {
                this.writeArray(val, buff);
            } else {
                this.writeObj(val, buff);
            }
        }
    }

    private static writeArray(arr: any[], buff: ByteBuffer) {
        buff.short(arr.length);
        for (let i = 0; i < arr.length; i++) {
            this.writeType(arr[i], buff);
        }
    }

    private static writeObj(obj: any, buff: ByteBuffer) {
        let keys = Object.keys(obj);
        keys.sort();
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i];
            this.writeVal(obj[key], buff);
        }
    }

    static pack(head: number, obj: any) {
        let a = new ByteBuffer();
        a.int32(head);
        let b = new ByteBuffer();
        this.writeObj(obj, b);
        b.toBuffer();
        a.int32(b.length);
        a.append(b);
        return a.buffer;
    }

    static unpack<T>(buffer: Buffer | null, obj: T) {
        if (buffer == null) {
            return { head: 0, obj: null };
        }
        let a = new ByteBuffer(buffer);
        let head = a.int32();
        let len = a.int32();
        this.readObj(obj, a);
        return { head: head, obj: obj };
    }
}